﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Ccr.Core;
using XcoAppSpaces.Contracts.CcrDispatching;
using XcoAppSpaces.Contracts.CcrWiring;
using XcoAppSpaces.Contracts.Communication;
using XcoAppSpaces.Contracts.Transport;
using XcoAppSpaces.Contracts.Exceptions;
using XcoAppSpaces.Contracts.Security;
using XcoAppSpaces.Contracts.Service;
using XcoAppSpaces.Contracts.Serialization;
using XcoAppSpaces.Core.CcrDispatching;

namespace XcoAppSpaces.Core.Configuration
{
	/// <summary>
	/// Fluent interface for creating an XcoAppSpace.
	/// </summary>
	public class XcoAppSpaceConfig : IXcoConfiguration
	{
		internal Dictionary<Type, List<XcoServiceConfig>> ServiceConfigs { get; private set; }
		
		private const string DefaultServiceName = "Default";

		/// <summary>
		/// The configured ccr dispatcher instance (or null, if no special instance has been configured).
		/// </summary>
		public Dispatcher CcrDispatcher { get; set; }

		/// <summary>
		/// Creates a new XcoAppSpaceConfiguration.
		/// </summary>
		public XcoAppSpaceConfig()
		{
			this.ServiceConfigs = new Dictionary<Type, List<XcoServiceConfig>>();
		}

		/// <summary>
		/// Adds a service to the XcoAppSpaceConfiguration instance.
		/// </summary>
		/// <param name="service">The service instance that should be added.</param>
		/// <returns>An XcoServiceConfiguration instance that allows further configuration of the service.</returns>
		public XcoServiceConfig AddService(IXcoService service)
		{
			return AddService(service, null, false);
		}

		/// <summary>
		/// Defines a service that the XcoAppSpace should use (if not defined, the default for this type of service is used). An
		/// exception is thrown if the type of service is unknown.
		/// </summary>
		/// <param name="service">The service that the XcoAppSpace should use.</param>
		/// <param name="name">The name of the service.</param>
		/// <param name="isDefault">True if the service is the default.</param>
		/// <returns>An XcoServiceConfiguration instance to configure the service.</returns>
		public XcoServiceConfig AddService(IXcoService service, string name, bool isDefault)
		{
			var serviceType = GetServiceType(service);
			if (!ServiceConfigs.ContainsKey(serviceType))
				ServiceConfigs.Add(serviceType, new List<XcoServiceConfig>());
			var serviceConfig = new XcoServiceConfig(this, service, serviceType) { Name = name, IsDefault = isDefault };
			ServiceConfigs[serviceType].Add(serviceConfig);
			return serviceConfig;
		}

		private static Type GetServiceType(IXcoService service)
		{
			if (service is IXcoTransportService)
				return typeof (IXcoTransportService);
			if (service is IXcoSerializer)
				return typeof(IXcoSerializer);
			if (service is IXcoSecurityService)
				return typeof(IXcoSecurityService);
			if (service is ICcrDispatcherService)
				return typeof(ICcrDispatcherService);
			return service.GetType();
		}

		#region IXcoConfiguration Members

		/// <summary>
		/// Initializes the service registry by adding instances for all services that are part of this
		/// XcoAppSpaceConfiguration instance. For any service that has not been configured explicitly,
		/// an instance of the default service for this service type is added.
		/// </summary>
		/// <param name="reg">The service registry that should be initalized.</param>
		public void InitServiceRegistry(IXcoServiceRegistry reg)
		{
			RegisterDefault<ICcrWirer>(reg);
			RegisterDefault<IConnector>(reg);

			RegisterConfiguredServices(reg);
			RegisterCcrDispatcherServiceIfNeeded(reg);
			RegisterDefaultIfNeeded<IXcoSerializer>(reg);
			RegisterDefaultIfNeeded<IXcoSecurityService>(reg);
		}

		private void RegisterCcrDispatcherServiceIfNeeded(IXcoServiceRegistry reg)
		{
			if (reg.Resolve<ICcrDispatcherService>() != null)
				return;

			if (CcrDispatcher != null)
				reg.Register(new CcrDispatcherService(CcrDispatcher));
			else
				reg.Register(new CcrDispatcherService());
		}

		private static void RegisterDefault<T>(IXcoServiceRegistry reg) where T : IXcoService
		{
			reg.Register(new XcoServiceConfiguration
			             	{
			             		Service = DefaultServices.GetDefault<T>(),
			             		ServiceType = typeof (T),
			             		Name = GetDefaultName(typeof (T)),
			             		IsDefault = true
			             	});
		}

		private static void RegisterDefaultIfNeeded<T>(IXcoServiceRegistry reg) where T : IXcoService
		{
			if (reg.Resolve<T>() == null)
				RegisterDefault<T>(reg);
		}

		private void RegisterConfiguredServices(IXcoServiceRegistry reg)
		{
			foreach (var services in ServiceConfigs)
			{
				if (!services.Value.Any(s => s.IsDefault))
					services.Value[0].IsDefault = true;
				if (services.Value.Count == 1)
				{
					if (services.Value[0].Name == null)
						services.Value[0].Name = GetDefaultName(services.Key);
					reg.Register((XcoServiceConfiguration)services.Value[0]);
				}
				else
				{
					foreach (var service in services.Value)
					{
						if (!service.IsDefault && service.Name == null)
							throw new XcoConfigurationException("All non-default services must define a name!");
						if (service.Name == null)
							service.Name = GetDefaultName(services.Key);
						reg.Register((XcoServiceConfiguration)service);
					}
				}
			}
		}

		private static string GetDefaultName(Type serviceType)
		{
			return serviceType.Name + DefaultServiceName;
		}

		#endregion
	}
}
